FluentCompositeKeyMappingConfigurationSupport.java
package org.codefilarete.stalactite.engine.configurer;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiConsumer;
import org.codefilarete.reflection.AccessorByMethod;
import org.codefilarete.reflection.AccessorByMethodReference;
import org.codefilarete.reflection.AccessorChain;
import org.codefilarete.reflection.AccessorDefinition;
import org.codefilarete.reflection.MethodReferenceCapturer;
import org.codefilarete.reflection.MethodReferenceDispatcher;
import org.codefilarete.reflection.MutatorByMethod;
import org.codefilarete.reflection.MutatorByMethodReference;
import org.codefilarete.reflection.PropertyAccessor;
import org.codefilarete.reflection.ReversibleAccessor;
import org.codefilarete.reflection.ValueAccessPointMap;
import org.codefilarete.reflection.ValueAccessPointSet;
import org.codefilarete.stalactite.dsl.naming.ColumnNamingStrategy;
import org.codefilarete.stalactite.dsl.key.CompositeKeyMappingConfiguration;
import org.codefilarete.stalactite.dsl.key.CompositeKeyMappingConfigurationProvider;
import org.codefilarete.stalactite.dsl.key.CompositeKeyPropertyOptions;
import org.codefilarete.stalactite.dsl.key.FluentCompositeKeyMappingBuilder;
import org.codefilarete.stalactite.dsl.embeddable.ImportedEmbedOptions;
import org.codefilarete.stalactite.engine.configurer.PropertyAccessorResolver.PropertyMapping;
import org.codefilarete.stalactite.sql.ddl.Size;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.statement.binder.ParameterBinder;
import org.codefilarete.stalactite.sql.statement.binder.ParameterBinderRegistry.EnumBindType;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.function.SerializableThrowingFunction;
import org.codefilarete.tool.function.SerializableTriFunction;
import org.codefilarete.tool.function.ThreadSafeLazyInitializer;
import org.danekja.java.util.function.serializable.SerializableBiConsumer;
import org.danekja.java.util.function.serializable.SerializableFunction;
/**
* @author Guillaume Mary
*/
public class FluentCompositeKeyMappingConfigurationSupport<C> implements FluentCompositeKeyMappingBuilder<C>, LambdaMethodUnsheller,
CompositeKeyMappingConfiguration<C> {
private CompositeKeyMappingConfigurationProvider<? super C> superMappingBuilder;
/** Owning class of mapped properties */
private final Class<C> classToPersist;
@Nullable
private ColumnNamingStrategy columnNamingStrategy;
/** Mapping definitions */
protected final List<CompositeKeyLinkage> mapping = new ArrayList<>();
/** Collection of embedded elements, even inner ones to help final build process */
private final Collection<Inset<C, ?>> insets = new ArrayList<>();
/** Last embedded element, introduced to help inner embedding registration (kind of algorithm help). Has no purpose in whole mapping configuration. */
private Inset<C, ?> currentInset;
/** Helper to unshell method references */
private final MethodReferenceCapturer methodSpy;
/**
* Creates a builder to map the given class for persistence
*
* @param classToPersist the class to create a mapping for
*/
public FluentCompositeKeyMappingConfigurationSupport(Class<C> classToPersist) {
this.classToPersist = classToPersist;
// Helper to capture Method behind method reference
this.methodSpy = new MethodReferenceCapturer();
}
@Override
public Class<C> getBeanType() {
return classToPersist;
}
@Override
public Collection<Inset<C, Object>> getInsets() {
return (Collection) insets;
}
@Override
public CompositeKeyMappingConfiguration<? super C> getMappedSuperClassConfiguration() {
return superMappingBuilder == null ? null : superMappingBuilder.getConfiguration();
}
@Override
@Nullable
public ColumnNamingStrategy getColumnNamingStrategy() {
return columnNamingStrategy;
}
@Override
public List<CompositeKeyLinkage> getPropertiesMapping() {
return mapping;
}
@Override
public CompositeKeyMappingConfiguration<C> getConfiguration() {
return this;
}
@Override
public Method captureLambdaMethod(SerializableFunction getter) {
return this.methodSpy.findMethod(getter);
}
@Override
public Method captureLambdaMethod(SerializableBiConsumer setter) {
return this.methodSpy.findMethod(setter);
}
@Override
public FluentCompositeKeyMappingConfigurationSupport<C> withColumnNaming(ColumnNamingStrategy columnNamingStrategy) {
this.columnNamingStrategy = columnNamingStrategy;
return this;
}
/**
* Gives access to currently configured {@link Inset}. Made so one can access features of {@link Inset} which are wider than
* the one available through {@link FluentCompositeKeyMappingBuilder}.
*
* @return the last {@link Inset} built by {@link #newInset(SerializableFunction, CompositeKeyMappingConfigurationProvider)}
* or {@link #newInset(SerializableBiConsumer, CompositeKeyMappingConfigurationProvider)}
*/
protected Inset<C, ?> currentInset() {
return currentInset;
}
protected <O> Inset<C, O> newInset(SerializableFunction<C, O> getter, CompositeKeyMappingConfigurationProvider<? extends O> CompositeKeyMappingBuilder) {
currentInset = new Inset<>(getter, CompositeKeyMappingBuilder, this);
return (Inset<C, O>) currentInset;
}
protected <O> Inset<C, O> newInset(SerializableBiConsumer<C, O> setter, CompositeKeyMappingConfigurationProvider<? extends O> CompositeKeyMappingBuilder) {
currentInset = new Inset<>(setter, CompositeKeyMappingBuilder, this);
return (Inset<C, O>) currentInset;
}
@Override
public <O> FluentCompositeKeyMappingBuilderPropertyOptions<C> map(SerializableBiConsumer<C, O> setter) {
return wrapWithPropertyOptions(addMapping(setter));
}
@Override
public <O> FluentCompositeKeyMappingBuilderPropertyOptions<C> map(SerializableFunction<C, O> getter) {
return wrapWithPropertyOptions(addMapping(getter));
}
<E> LinkageSupport<C, E> addMapping(SerializableBiConsumer<C, E> setter) {
LinkageSupport<C, E> newLinkage = new LinkageSupport<>(setter);
mapping.add(newLinkage);
return newLinkage;
}
<E> LinkageSupport<C, E> addMapping(SerializableFunction<C, E> getter) {
LinkageSupport<C, E> newLinkage = new LinkageSupport<>(getter);
mapping.add(newLinkage);
return newLinkage;
}
<O> FluentCompositeKeyMappingBuilderPropertyOptions<C> wrapWithPropertyOptions(LinkageSupport<C, O> linkage) {
return new MethodReferenceDispatcher()
.redirect(CompositeKeyPropertyOptions.class, new CompositeKeyPropertyOptions() {
@Override
public CompositeKeyPropertyOptions columnName(String name) {
linkage.setColumnName(name);
return null;
}
@Override
public CompositeKeyPropertyOptions columnSize(Size size) {
linkage.setColumnSize(size);
return null;
}
@Override
public CompositeKeyPropertyOptions fieldName(String name) {
linkage.setField(FluentCompositeKeyMappingConfigurationSupport.this.classToPersist, name);
return null;
}
}, true)
.fallbackOn(this)
.build((Class<FluentCompositeKeyMappingBuilderPropertyOptions<C>>) (Class) FluentCompositeKeyMappingBuilderPropertyOptions.class);
}
@Override
public <E extends Enum<E>> FluentCompositeKeyMappingBuilderEnumOptions<C> mapEnum(SerializableBiConsumer<C, E> setter) {
LinkageSupport<C, E> linkage = addMapping(setter);
return wrapWithEnumOptions(linkage);
}
@Override
public <E extends Enum<E>> FluentCompositeKeyMappingBuilderEnumOptions<C> mapEnum(SerializableFunction<C, E> getter) {
LinkageSupport<C, E> linkage = addMapping(getter);
return wrapWithEnumOptions(linkage);
}
<O extends Enum> FluentCompositeKeyMappingBuilderEnumOptions<C> wrapWithEnumOptions(LinkageSupport<C, O> linkage) {
return new MethodReferenceDispatcher()
.redirect(CompositeKeyEnumOptions.class, new CompositeKeyEnumOptions() {
@Override
public CompositeKeyEnumOptions byName() {
linkage.setEnumBindType(EnumBindType.NAME);
return null; // we can return null because dispatcher will return proxy
}
@Override
public CompositeKeyEnumOptions byOrdinal() {
linkage.setEnumBindType(EnumBindType.ORDINAL);
return null; // we can return null because dispatcher will return proxy
}
}, true)
.redirect(CompositeKeyPropertyOptions.class, new CompositeKeyPropertyOptions() {
@Override
public CompositeKeyPropertyOptions columnName(String name) {
linkage.setColumnName(name);
return null;
}
@Override
public CompositeKeyPropertyOptions columnSize(Size size) {
linkage.setColumnSize(size);
return null;
}
@Override
public CompositeKeyPropertyOptions fieldName(String name) {
linkage.setField(FluentCompositeKeyMappingConfigurationSupport.this.classToPersist, name);
return null;
}
}, true)
.fallbackOn(this)
.build((Class<FluentCompositeKeyMappingBuilderEnumOptions<C>>) (Class) FluentCompositeKeyMappingBuilderEnumOptions.class);
}
@Override
public FluentCompositeKeyMappingBuilder<C> mapSuperClass(CompositeKeyMappingConfigurationProvider<? super C> superMappingConfiguration) {
this.superMappingBuilder = superMappingConfiguration;
return this;
}
@Override
public <O> FluentCompositeKeyMappingBuilderCompositeKeyMappingConfigurationImportedEmbedOptions<C, O> embed(
SerializableFunction<C, O> getter,
CompositeKeyMappingConfigurationProvider<? extends O> compositeKeyMappingBuilder) {
return addImportedInset(newInset(getter, compositeKeyMappingBuilder));
}
@Override
public <O> FluentCompositeKeyMappingBuilderCompositeKeyMappingConfigurationImportedEmbedOptions<C, O> embed(
SerializableBiConsumer<C, O> setter,
CompositeKeyMappingConfigurationProvider<? extends O> compositeKeyMappingBuilder) {
return addImportedInset(newInset(setter, compositeKeyMappingBuilder));
}
private <O> FluentCompositeKeyMappingBuilderCompositeKeyMappingConfigurationImportedEmbedOptions<C, O> addImportedInset(Inset<C, O> inset) {
insets.add(inset);
return new MethodReferenceDispatcher()
.redirect(ImportedEmbedOptions.class, new ImportedEmbedOptions<C>() {
@Override
public <IN> ImportedEmbedOptions<C> overrideName(SerializableFunction<C, IN> getter, String columnName) {
inset.overrideName(getter, columnName);
return null; // we can return null because dispatcher will return proxy
}
@Override
public <IN> ImportedEmbedOptions<C> overrideName(SerializableBiConsumer<C, IN> setter, String columnName) {
inset.overrideName(setter, columnName);
return null; // we can return null because dispatcher will return proxy
}
@Override
public <IN> ImportedEmbedOptions<C> overrideSize(SerializableFunction<C, IN> getter, Size columnSize) {
inset.overrideSize(getter, columnSize);
return null; // we can return null because dispatcher will return proxy
}
@Override
public <IN> ImportedEmbedOptions<C> overrideSize(SerializableBiConsumer<C, IN> setter, Size columnSize) {
inset.overrideSize(setter, columnSize);
return null; // we can return null because dispatcher will return proxy
}
@Override
public ImportedEmbedOptions exclude(SerializableBiConsumer setter) {
inset.exclude(setter);
return null; // we can return null because dispatcher will return proxy
}
@Override
public ImportedEmbedOptions exclude(SerializableFunction getter) {
inset.exclude(getter);
return null; // we can return null because dispatcher will return proxy
}
}, true)
.fallbackOn(this)
.build((Class<FluentCompositeKeyMappingBuilderCompositeKeyMappingConfigurationImportedEmbedOptions<C, O>>) (Class) FluentCompositeKeyMappingBuilderCompositeKeyMappingConfigurationImportedEmbedOptions.class);
}
/**
* Small contract for mapping definition storage. See add(..) methods.
*
* @param <T> property owner type
*/
protected static class LinkageSupport<T, O> implements CompositeKeyLinkage<T, O> {
/**
* Optional binder for this mapping.
* May be not of column type in cases of converted data with {@link ParameterBinder#preApply(SerializableThrowingFunction)}
* or {@link ParameterBinder#thenApply(SerializableThrowingFunction)}.
*/
private ParameterBinder<Object> parameterBinder;
@Nullable
private EnumBindType enumBindType;
@Nullable
private String columnName;
@Nullable
private Size columnSize;
private final ValueAccessPointVariantSupport<T, O> accessor;
private String fieldName;
public LinkageSupport(SerializableFunction<T, O> getter) {
this.accessor = new ValueAccessPointVariantSupport<>(getter);
}
public LinkageSupport(SerializableBiConsumer<T, O> setter) {
this.accessor = new ValueAccessPointVariantSupport<>(setter);
}
@Override
@Nullable
public ParameterBinder<Object> getParameterBinder() {
return parameterBinder;
}
public void setParameterBinder(@Nullable ParameterBinder<?> parameterBinder) {
this.parameterBinder = (ParameterBinder<Object>) parameterBinder;
}
@Nullable
@Override
public EnumBindType getEnumBindType() {
return enumBindType;
}
public void setEnumBindType(@Nullable EnumBindType enumBindType) {
this.enumBindType = enumBindType;
}
@Nullable
@Override
public String getColumnName() {
return this.columnName;
}
public void setColumnName(String name) {
this.columnName = name;
}
@Nullable
@Override
public Size getColumnSize() {
return this.columnSize;
}
public void setColumnSize(@Nullable Size columnSize) {
this.columnSize = columnSize;
}
@Override
public ReversibleAccessor<T, O> getAccessor() {
return accessor.getAccessor();
}
@Nullable
@Override
public String getFieldName() {
return fieldName;
}
public void setField(Class<T> classToPersist, String fieldName) {
this.fieldName = fieldName;
// Note that getField(..) will throw an exception if field is not found, at the opposite of findField(..)
this.accessor.setField(classToPersist, fieldName);
}
@Override
public Class<O> getColumnType() {
return (Class<O>) AccessorDefinition.giveDefinition(this.accessor.getAccessor()).getMemberType();
}
}
/**
* Information storage of embedded mapping defined externally by an {@link CompositeKeyMappingConfigurationProvider},
* see {@link #embed(SerializableFunction, CompositeKeyMappingConfigurationProvider)}
*
* @param <SRC>
* @param <TRGT>
* @see #embed(SerializableFunction, CompositeKeyMappingConfigurationProvider)}
* @see #embed(SerializableBiConsumer, CompositeKeyMappingConfigurationProvider)}
*/
public static class Inset<SRC, TRGT> {
private final Class<TRGT> embeddedClass;
private final Method insetAccessor;
/** Equivalent of {@link #insetAccessor} as a {@link PropertyAccessor} */
private final PropertyAccessor<SRC, TRGT> accessor;
private final ValueAccessPointMap<SRC, String> overriddenColumnNames = new ValueAccessPointMap<>();
private final ValueAccessPointMap<SRC, Size> overriddenColumnSizes = new ValueAccessPointMap<>();
private final ValueAccessPointSet<SRC> excludedProperties = new ValueAccessPointSet<>();
private final CompositeKeyMappingConfigurationProvider<? extends TRGT> configurationProvider;
private final ValueAccessPointMap<SRC, Column> overriddenColumns = new ValueAccessPointMap<>();
Inset(SerializableBiConsumer<SRC, TRGT> targetSetter,
CompositeKeyMappingConfigurationProvider<? extends TRGT> configurationProvider,
LambdaMethodUnsheller lambdaMethodUnsheller) {
this.insetAccessor = lambdaMethodUnsheller.captureLambdaMethod(targetSetter);
this.accessor = new PropertyAccessor<>(
new MutatorByMethod<SRC, TRGT>(insetAccessor).toAccessor(),
new MutatorByMethodReference<>(targetSetter));
// looking for the target type because it's necessary to find its persister (and other objects)
this.embeddedClass = Reflections.javaBeanTargetType(getInsetAccessor());
this.configurationProvider = configurationProvider;
}
Inset(SerializableFunction<SRC, TRGT> targetGetter,
CompositeKeyMappingConfigurationProvider<? extends TRGT> configurationProvider,
LambdaMethodUnsheller lambdaMethodUnsheller) {
this.insetAccessor = lambdaMethodUnsheller.captureLambdaMethod(targetGetter);
this.accessor = new PropertyAccessor<>(
new AccessorByMethodReference<>(targetGetter),
new AccessorByMethod<SRC, TRGT>(insetAccessor).toMutator());
// looking for the target type because it's necessary to find its persister (and other objects)
this.embeddedClass = Reflections.javaBeanTargetType(getInsetAccessor());
this.configurationProvider = configurationProvider;
}
/**
* Equivalent of {@link #insetAccessor} as a {@link PropertyAccessor}
*/
public PropertyAccessor<SRC, TRGT> getAccessor() {
return accessor;
}
/**
* Equivalent of given getter or setter at construction time as a {@link Method}
*/
public Method getInsetAccessor() {
return insetAccessor;
}
public Class<TRGT> getEmbeddedClass() {
return embeddedClass;
}
public ValueAccessPointSet<SRC> getExcludedProperties() {
return this.excludedProperties;
}
public ValueAccessPointMap<SRC, String> getOverriddenColumnNames() {
return this.overriddenColumnNames;
}
public ValueAccessPointMap<SRC, Size> getOverriddenColumnSizes() {
return overriddenColumnSizes;
}
public ValueAccessPointMap<SRC, Column> getOverriddenColumns() {
return overriddenColumns;
}
public CompositeKeyMappingConfigurationProvider<TRGT> getConfigurationProvider() {
return (CompositeKeyMappingConfigurationProvider<TRGT>) configurationProvider;
}
public void overrideName(SerializableFunction methodRef, String columnName) {
this.overriddenColumnNames.put(new AccessorByMethodReference(methodRef), columnName);
}
public void overrideName(SerializableBiConsumer methodRef, String columnName) {
this.overriddenColumnNames.put(new MutatorByMethodReference(methodRef), columnName);
}
public void overrideName(AccessorChain accessorChain, String columnName) {
this.overriddenColumnNames.put(accessorChain, columnName);
}
public void overrideSize(SerializableFunction methodRef, Size columnSize) {
this.overriddenColumnSizes.put(new AccessorByMethodReference(methodRef), columnSize);
}
public void overrideSize(SerializableBiConsumer methodRef, Size columnSize) {
this.overriddenColumnSizes.put(new MutatorByMethodReference(methodRef), columnSize);
}
public void overrideSize(AccessorChain accessorChain, Size columnSize) {
this.overriddenColumnSizes.put(accessorChain, columnSize);
}
public void override(SerializableFunction methodRef, Column column) {
this.overriddenColumns.put(new AccessorByMethodReference(methodRef), column);
}
public void override(SerializableBiConsumer methodRef, Column column) {
this.overriddenColumns.put(new MutatorByMethodReference(methodRef), column);
}
public void exclude(SerializableBiConsumer methodRef) {
this.excludedProperties.add(new MutatorByMethodReference(methodRef));
}
public void exclude(SerializableFunction methodRef) {
this.excludedProperties.add(new AccessorByMethodReference(methodRef));
}
}
}